using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Newtonsoft.Json.Linq;
using System.Xml.Linq;

namespace wamr.Worker
{
    public class WorkerRole : RoleEntryPoint
    {

        // WAMR related storage variables and controls
        private static object storageInitializedGate = new object();
        private static bool storageInitialized = false;
        private Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient wamrBlobStorage;
        private Microsoft.WindowsAzure.Storage.Queue.CloudQueueClient wamrQueueStorage;
        private Microsoft.WindowsAzure.Storage.Queue.CloudQueue wamrQueue;

        // task list
        private List<ManagerTask> ListOfManagerTasks = new List<ManagerTask>();

        // status loop counter - only check status every xx loops 
        // this is to prevent status checking from tying up all the time
        const int STATUSLOOPMAX = 30; // 30 * 10 seconds = 300 seconds = 5 minutes
        private int statusLoopCount = 0;

        public override void Run()
        {
            // This is a sample worker implementation. Replace with your logic.
            Trace.TraceInformation("wamr.Worker entry point called");

            while (true)
            {
                try
                {
                    Microsoft.WindowsAzure.Storage.Queue.CloudQueueMessage msg = wamrQueue.GetMessage();
                    while (msg != null)
                    {
                        Trace.TraceInformation(string.Format("Message received: {0}", msg.AsString));

                        if (msg.DequeueCount > 3)
                        {
                            wamrQueue.DeleteMessage(msg);
                            // probably should put a copy of the message somewhere so we can figure out why it doesn't work
                        }

                        // this routine figures out what kind of task it is
                        // and adds the task to the task list.
                        InterpretManagerMessage(msg.AsString);

                        // delete the message off of the queue
                        wamrQueue.DeleteMessage(msg);

                        msg = wamrQueue.GetMessage();
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error occurred processing message acquisition and processing in WorkerRole.Run(): {0}", ex.Message);
                }


                try
                {
                    // check for tasks that are waiting for preconditions or are new
                    foreach (ManagerTask m in ListOfManagerTasks)
                    {
                        if ((m.TaskDisposition == ManagerTask.TaskDispositionEnum.NONE) ||
                            (m.TaskDisposition == ManagerTask.TaskDispositionEnum.WAIT))
                        {   // new manager task in the list (NONE) or a task that is waiting for something (WAIT)
                            if (!m.PreConditionsMet())
                            {   // some pre-condition not met
                                if (m.TaskDisposition == ManagerTask.TaskDispositionEnum.FAIL)
                                {
                                    m.TaskDisposition = ManagerTask.TaskDispositionEnum.DELETE;
                                    DeleteDependencyChain(m);
                                }
                            }
                            if (!m.Initiate())
                            {   // initiation failed
                                if (m.TaskDisposition == ManagerTask.TaskDispositionEnum.FAIL)
                                {
                                    m.TaskDisposition = ManagerTask.TaskDispositionEnum.DELETE;
                                    DeleteDependencyChain(m);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error occurred in WorkerRole, Initiate task loop: {0}", ex.Message);
                }

                if ((ListOfManagerTasks.Count > 0) && (statusLoopCount++ > STATUSLOOPMAX))
                {
                    Stopwatch sw = new Stopwatch();
                    try
                    {
                        // check status of running tasks
                        foreach (ManagerTask m in ListOfManagerTasks)
                        {
                            if (m.TaskDisposition == ManagerTask.TaskDispositionEnum.PROCEED)
                            {
                                sw.Reset();
                                sw.Start();
                                if (m.Status())
                                {
                                    // takes about 200ms
                                    Trace.TraceInformation("Timing - Status call in milliseconds - {0}", sw.ElapsedMilliseconds);

                                    if (m.TaskDisposition == ManagerTask.TaskDispositionEnum.FAIL)
                                    {
                                        Trace.TraceWarning("Task failed: {0}", (string.IsNullOrEmpty(m.TaskType) ? "null" : m.TaskType));

                                        m.TaskDisposition = ManagerTask.TaskDispositionEnum.DELETE;
                                        DeleteDependencyChain(m);
                                    }
                                    else
                                    {
                                        if (m.TaskDisposition == ManagerTask.TaskDispositionEnum.COMPLETE)
                                        {
                                            Trace.TraceInformation("Task complete: {0}", (string.IsNullOrEmpty(m.TaskType) ? "null" : m.TaskType));
                                            if (m.Dependency != null)
                                            {  // where there is a dependency and the dependency completed, 
                                                // let the dependent task know that its dependency is resolved.
                                                foreach (ManagerTask DependentTask in ListOfManagerTasks)
                                                {
                                                    if (m.Dependency == DependentTask.DependsOn)
                                                        DependentTask.ResolveDependency();
                                                }
                                            }

                                            m.TaskDisposition = ManagerTask.TaskDispositionEnum.DELETE;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError("Error occurrect in WorkerRole, status check loop: {0}", ex.Message);
                    }
                    statusLoopCount = 0;
                }
                // clean up deleted tasks
                ListOfManagerTasks.RemoveAll(ReadyToDelete);

                // log the number of tasks 
                Trace.WriteLine(string.Format("Processing {0} tasks.", ListOfManagerTasks.Count));

                Thread.Sleep(10000);
                Trace.WriteLine("Working", "Information");
            }
        }

        private void DeleteDependencyChain(ManagerTask m)
        {
            if (m.Dependency == null) return;

            foreach (ManagerTask DependentTask in ListOfManagerTasks)
            {
                if (m.Dependency == DependentTask.DependsOn)
                {
                    DependentTask.TaskDisposition = ManagerTask.TaskDispositionEnum.DELETE;
                    DeleteDependencyChain(DependentTask);
                }
            }
        }

        private static bool ReadyToDelete(ManagerTask m)
        {
            return m.TaskDisposition == ManagerTask.TaskDispositionEnum.DELETE;
        }

        private void InterpretManagerMessage(string ManagerMessage)
        {
            ManagerMessage = DetectAzureSchedulerMessage(ManagerMessage);

            JObject jsonMsg;
            JProperty p;
            try
            {
                jsonMsg = JObject.Parse(ManagerMessage);
                p = jsonMsg.Properties().First<JProperty>();
            }
            catch (Newtonsoft.Json.JsonReaderException ex)
            {
                Trace.TraceError(string.Format("Error parsing JSON input: {0}", ex.Message));
                return;
            }

            // special section for composite tasks
            if (p.Name == "backup")
            {
                BackupDatabaseTask bdt = new BackupDatabaseTask(ManagerMessage);
                if (bdt.MessageIsValid)
                {
                    List<string> CompositeTaskList = bdt.TaskList();
                    foreach (string s in CompositeTaskList)
                        InterpretManagerMessage(s);
                }
            }   // other composite tasks?
            else
            {   // not a composite task
                AppendTaskToList(p.Name, ManagerMessage);
            }
        }

        private string DetectAzureSchedulerMessage(string ManagerMessage)
        {
            string returnValue = ManagerMessage;

            if (ManagerMessage[0] == '<')
            {
                XElement root = XElement.Parse(ManagerMessage);

                var element = (from el in root.Elements("Message")
                              select el).FirstOrDefault();

                returnValue = element.Value;
            }

            Trace.TraceInformation("Value returned from DetectAzureSchedulerMessage: {0}", returnValue);

            return returnValue;
        }

        private void AppendTaskToList(string TaskType, string ManagerMessage)
        {
            if (TaskType == "copy")
                ListOfManagerTasks.Add(new CopyDatabaseTask(ManagerMessage));
            else if (TaskType == "export")
                ListOfManagerTasks.Add(new ExportDatabaseTask(ManagerMessage));
            else if (TaskType == "createdeployment")
                ListOfManagerTasks.Add(new DeployServiceTask(ManagerMessage));
            else if (TaskType == "deletedeployment")
                ListOfManagerTasks.Add(new DeleteDeploymentTask(ManagerMessage));
            else if (TaskType == "copyblob")
                ListOfManagerTasks.Add(new CopyBlobTask(ManagerMessage));
        }
        
        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            // log the start
            Trace.TraceInformation("WorkerRole.OnStart processing.");

            // initialize the storage account
            InitializeStorage();

            return base.OnStart();
        }

        private void InitializeStorage()
        {
            if (storageInitialized)
            {
                return;
            }

            Trace.TraceInformation("WorkerRole.InitializeStorage processing.");

            lock (storageInitializedGate)
            {
                if (storageInitialized)
                {
                    return;
                }

                try
                {
                    //
                    // the manager storage account contains:
                    // 1. WAMR_QUEUE_NAME - name of a queue that brings messages to WAMR
                    // 2. WAMR diagnostics
                    //
                    // read wamr storage account configuration settings
                    string storageAccountName = CloudConfigurationManager.GetSetting(Settings.WAMR_STORAGE_SETTING);
                    string queueName = CloudConfigurationManager.GetSetting(Settings.WAMR_QUEUE_SETTING);

                    // parse the connection string & create object
                    Microsoft.WindowsAzure.Storage.CloudStorageAccount storageAccount;
                    if (Microsoft.WindowsAzure.Storage.CloudStorageAccount.TryParse(storageAccountName, out storageAccount))
                    {
                        // create the blob storage client
                        wamrBlobStorage = storageAccount.CreateCloudBlobClient();

                        // and the queue
                        wamrQueueStorage = storageAccount.CreateCloudQueueClient();
                        wamrQueue = wamrQueueStorage.GetQueueReference(queueName);
                        wamrQueue.CreateIfNotExists();
                    }

                }
                catch (WebException)
                {
                    throw new WebException("Storage services initialization failure. "
                        + "Check your storage account configuration settings. If running locally, "
                        + "ensure that the Development Storage service is running.");
                }
                catch (Exception ex)
                {
                    Trace.TraceError(string.Format("Error initializing storage: {0}", ex.InnerException));
                    return;
                }

                storageInitialized = true;
            }
        }
    }
    

    public static class StringExtensions
    {
        /// <summary>
        /// Converts a UTF-8 string to a Base-64 version of the string.
        /// </summary>
        /// <param name="s">The string to convert to Base-64.</param>
        /// <returns>The Base-64 converted string.</returns>
        public static string ToBase64(this string s)
        {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(s);
            return Convert.ToBase64String(bytes);
        }

        /// <summary>
        /// Converts a Base-64 encoded string to UTF-8.
        /// </summary>
        /// <param name="s">The string to convert from Base-64.</param>
        /// <returns>The converted UTF-8 string.</returns>
        public static string FromBase64(this string s)
        {
            byte[] bytes = Convert.FromBase64String(s);
            return System.Text.Encoding.UTF8.GetString(bytes);
        }
    }
}
